-module(koordinator).
-export([initializeSetNeighbours/1, start/0, initialize/0]).

start() -> spawn(fun() -> koordinator:initialize() end).


initialize() -> {ok, ConfigListe} 		= file:consult("koordinator.cfg"),
			    {ok, Arbeitszeit} 		= werkzeug:get_config_value(arbeitszeit, ConfigListe),
			    {ok, Termzeit} 			= werkzeug:get_config_value(termzeit, ConfigListe),
			    {ok, Ggtprozessnummer} 	= werkzeug:get_config_value(ggtprozessnummer, ConfigListe),
			    {ok, Nameservicenode} 	= werkzeug:get_config_value(nameservicenode, ConfigListe),
			    {ok, Nameservicename} 	= werkzeug:get_config_value(nameservicename, ConfigListe),
			    {ok, Koordinatorname} 	= werkzeug:get_config_value(koordinatorname, ConfigListe),
			    {ok, Korrigieren} 		= werkzeug:get_config_value(korrigieren, ConfigListe),
				register(Koordinatorname,self()),
				net_adm:ping(Nameservicenode),
				timer:sleep(500), 									% for managing variables in the global node net
				Nameservice = global:whereis_name(Nameservicename), % nur damit man gleich die Variable f�r Nachrichten hat
				Nameservice ! {self(),{rebind,Koordinatorname,node()}},
				receive ok -> true end,
				initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, [] ).

				
reinitialize(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, _Korrigieren, GgTList) -> 		
				sendKillToGgTsR(Nameservice, GgTList),
				writeToLog(lists:concat([ " RESET durch Benutzer - Korrigieren wieder aus Konfig und leere GgTListe " ,"\r\n"])),
				{ok, ConfigListe} = file:consult("koordinator.cfg"),
				{ok, Korrigieren} = werkzeug:get_config_value(korrigieren, ConfigListe),
				initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, []).

				
% --------------------------------- the states of koordinator ------------------------------------
					

initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList) -> 
				receive		{hello, GgTName} 		-> 	ExtendedGgTList = lists:append([GgTName], GgTList),
														writeToLog(lists:concat([GgTName, " wurde in GgT-Liste aufgenommen " ,"\r\n"])),
														initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, ExtendedGgTList);
							
							{getsteeringval,Starter}-> 	writeToLog(lists:concat(["Einem starter wurden geforderte Werte �bergeben " ,"\r\n"])),
														Starter ! {steeringval,Arbeitszeit,Termzeit,Ggtprozessnummer},
														initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);	
							
							{briefmi,{GgTName,CMi,_CZeit}} ->
														writeToLog(lists:concat(["Veraltetes briefmi von GgT ", GgTName, " mit Wert: ", CMi ,"\r\n"])),
														initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);
							
							{briefterm,{GgTName,CMi,_CZeit},_From} ->
														writeToLog(lists:concat(["Veraltetes briefterm von GgT ", GgTName, " mit Wert: ", CMi ,"\r\n"])),
														initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);
							
							reset					-> 	reinitialize(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);
							
							step					-> 	CountOfGgTs = erlang:length(GgTList),
														if
															CountOfGgTs >= 2 -> 
																writeToLog(lists:concat([" -------------- STEP VOM BENUTZER -------------- " ,"\r\n"])),
																ListWithGgTsMappedOnNeighbours = initializeSetNeighbours(GgTList),
																buildTheRingR(ListWithGgTsMappedOnNeighbours, Nameservice),		
																bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, 0);
															true 			->
																writeToLog(lists:concat([" -------------- STEP VOM BENUTZER VERWEIGERT - WENIGER ALS ZWEI GgT Prozesse-------------- " ,"\r\n"])),
																initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList)
														end;
														

							prompt					-> 	writeToLog(lists:concat(["Der Benutzer hat keine Ahnung was er tut - PROMPT in initial" ,"\r\n"])),
														initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);
								
							nudge					-> 	commandToGgTsR(Nameservice, GgTList, pingGGT ),
														writeToLog(lists:concat([" An alle GgTs wurde PING  gesendet " ,"\r\n"])),
														initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);
														
							{pongGGT,GGTname}		-> 	writeToLog(lists:concat([" PONG von: ", GGTname ,"\r\n"])),
												initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);

							toggle					-> 	NewKorrigieren = toggleKorrigieren(Korrigieren),
														initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, NewKorrigieren, GgTList);
														
							{calc,_WggT}			-> 	writeToLog(lists:concat(["Der Benutzer hat keine Ahnung was er tut - {calc,WggT} in initial" ,"\r\n"])),
														initial(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);
								
							kill					-> 	beenden(Nameservice, GgTList, Koordinatorname)
				end.
	
	
				
% Arbeitsphase
bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, MinMi) -> 	
				receive 	{hello, GgTName} 		-> 	ExtendedGgTList = lists:append([GgTName], GgTList),
														writeToLog(lists:concat([GgTName, " kam zu spaet und wurde NICHT in GgT-Liste aufgenommen " ,"\r\n"])),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, ExtendedGgTList, MinMi);
							
							{getsteeringval,_Starter}->	writeToLog(lists:concat(["Ein Starter kam zu spaet und wurde NICHT mit Werten versorgt " ,"\r\n"])),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, MinMi);
							
							{briefmi,{GgTName,CMi,CZeit}} -> 
														if	
															MinMi == 0 	-> NewMinMi 	= CMi;
															MinMi > 0 	-> NewMinMi 	= erlang:min(MinMi, CMi)
														end,
														if 
															MinMi == 0 						-> State = "war das erste Mi";
															MinMi > 0 andalso MinMi > CMi 	-> State = "neues Mi hat koordinator Mi ersetzt";
															MinMi > 0 andalso MinMi =< CMi	-> State = "neues Mi war groe�er oder gleich als koordinator Mi";
															true 							-> State = "inkonsistenter Zustand"
														end,
														writeToLog(lists:concat(["Von GgT: ",GgTName, " neues Mi: ",CMi ," erhalten um: " ,CZeit, " nachfolgende Aktion: ", State ,"\r\n"])),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, NewMinMi);
														
							
							{briefterm,{GgTName,CMi,CZeit},From} ->
														if
															MinMi == 0 					  ->
																NewMinMi = CMi,
																writeToLog(lists:concat(["Terminierungsmeldung mit Mi: " ,CMi , " durch GgT: ", GgTName, " um: ", CZeit, " kein Korrigieren moeglich, weil erster wert" ,"\r\n"]));
															% ist das terminierungs-mi zu gro�? 
															CMi > MinMi andalso MinMi > 0 -> 
																writeToLog(lists:concat(["Terminierungsmeldung mit zu grossem Mi: " ,CMi , " durch GgT: ", GgTName, " um: ", CZeit ,"\r\n"])),
																% ist eine korrektur gew�nscht?
																if
																	Korrigieren == 1 -> 
																		From ! {sendy,MinMi},
																		writeToLog(lists:concat(["Korrigieren war aktiviert - Min Mi an Absender der Meldung geschickt","\r\n"]));
																	true 			-> true
																end,
																NewMinMi = MinMi;
															% ist das terminierungs-mi in ordnung ? 
															CMi =< MinMi andalso MinMi > 0 ->
																NewMinMi = CMi,
																writeToLog(lists:concat(["Terminierungsmeldung mit Mi: " ,CMi , " durch GgT: ", GgTName, " um: ", CZeit ,"\r\n"]))																
														end,
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, NewMinMi);

														
							reset					-> 	reinitialize(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList);
							
							step					->  writeToLog(lists:concat(["Der Benutzer hat keine Ahnung was er tut - STEP in bereit" ,"\r\n"])),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, MinMi);
							
							prompt					-> 	commandToGgTsR(Nameservice, GgTList, tellmi),
														writeToLog(lists:concat([" An alle GgTs wurde TELLMI gesendet " ,"\r\n"])),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, MinMi);
							
							{mi,Mi}					-> 	writeToLog(lists:concat([" Mi von ? GgT: ", Mi ,"\r\n"])),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, MinMi);
							
							nudge					->  commandToGgTsR(Nameservice, GgTList, pingGGT ),
														writeToLog(lists:concat([" An alle GgTs wurde PING  gesendet " ,"\r\n"])),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, MinMi);
							
							{pongGGT,GGTname}		-> 	writeToLog(lists:concat([" PONG von: ", GGTname ,"\r\n"])),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, MinMi);
								
							toggle					-> 	NewKorrigieren = toggleKorrigieren(Korrigieren),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, NewKorrigieren, GgTList, MinMi);
														
							{calc,WggT}				-> 	writeToLog(lists:concat([" BERECHNUNG GESTARTET mit Wunsch-GgT: ", WggT ,"\r\n"])),
														handleCalcGgT(Nameservice, GgTList, WggT),
														bereit(Arbeitszeit, Termzeit, Ggtprozessnummer, Nameservicenode, Nameservicename, Nameservice, Koordinatorname, Korrigieren, GgTList, MinMi);
							
							kill					-> 	beenden(Nameservice, GgTList, Koordinatorname)
				end.					


beenden(Nameservice, GgTList, Koordinatorname) -> 
							sendKillToGgTsR(Nameservice, GgTList),
							writeToLog(lists:concat(["Koordinator UNBIND, UNREGISTER und Prozess auslaufen lassen " ,"\r\n"])),
							Nameservice ! {self(),{unbind, Koordinatorname}},
							unregister(Koordinatorname).


% --------------------------------- helping functions ------------------------------------


commandToGgTsR(_Nameservice, [] , _Command) 	-> true;
commandToGgTsR(Nameservice, GgTList, Command) 	-> 	
						[FirstOfGgTList|RestOfGgTList] = GgTList,
						Nameservice ! {self(), {lookup, FirstOfGgTList}},
						receive 	
							{pin,{FirstOfGgTList,GgTNode}} 	->  {FirstOfGgTList,GgTNode} ! {Command, self()} ;
							not_found  						-> 	true %sollte behandelt werden damit die queue nicht "vollm�llt"
						end,
						commandToGgTsR(Nameservice, RestOfGgTList, Command).

						

handleCalcGgT(Nameservice, GgTList, WggT) -> 
						NeededCountOfMis 	= erlang:length(GgTList),
						GeneratedMis 		= werkzeug:bestimme_mis(WggT,NeededCountOfMis),
						% an alle GgTs das StartMi senden
						sendMiNeuToGgTsR(Nameservice, GgTList, GeneratedMis),
						triggerCalculation(Nameservice, GgTList, WggT).
						
						
triggerCalculation(Nameservice, GgTList, WggT) ->
						CountOfGgTs 			= erlang:length(GgTList),
						CountOfGgTsForMessage 	= erlang:round(CountOfGgTs * 0.15),
						if
							CountOfGgTsForMessage < 2 -> EndCountOfGgTsForMessage = 2;
							CountOfGgTsForMessage >= 2 -> EndCountOfGgTsForMessage = CountOfGgTsForMessage
						end,
						ShuffledList = werkzeug:shuffle(GgTList),
						GgTsToStart = lists:sublist(ShuffledList, EndCountOfGgTsForMessage),
						lists:foreach(
							fun(GgT)	-> 	[MiForGgT] = werkzeug:bestimme_mis(WggT,1),
											sendSendyToSingleGgT(Nameservice, GgT, MiForGgT) 
							end,
						GgTsToStart).
						
	
sendSendyToSingleGgT(Nameservice, GgT, Y) ->
						Nameservice ! {self(), {lookup, GgT}},
						receive 	
								{pin,{GgT,GgTNode}} 	-> {GgT,GgTNode} ! {sendy,Y};
								not_found 				-> true
						end.
						
				
sendMiNeuToGgTsR(_Nameservice, [], []) 	 				-> true;
sendMiNeuToGgTsR(Nameservice, GgTList, GeneratedMis)	->
						[ActualGgT|RestOfGgTs] 	= GgTList,
						[ActualMi|RestOfMis] 	= GeneratedMis,
						Nameservice ! {self(), {lookup, ActualGgT}},
						receive 	
								{pin,{ActualGgT,GgTNode}} 	-> {ActualGgT,GgTNode} ! {setpm,ActualMi};
								not_found 					-> true
						end,
						sendMiNeuToGgTsR(Nameservice, RestOfGgTs, RestOfMis).				


buildTheRingR([], _Nameservice)								-> true;
buildTheRingR(ListWithGgTsMappedOnNeighbours, Nameservice) 	-> 
						[{ActualGgT,{LeftNeighbour, RightNeighbour}}|RestOfListWithGgTsMappedOnNeighbours] = ListWithGgTsMappedOnNeighbours,
						Nameservice ! {self(), {lookup, ActualGgT}},
						receive 	
								{pin,{ActualGgT,GgTNode}} 	-> {ActualGgT,GgTNode} ! {setneighbors,LeftNeighbour,RightNeighbour};
								not_found 					-> true
											% also nur Nachricht aus queue nehmen
											% wenn der GgT nicht erreichbar ist , ist der Ring eigentlich schon zerst�rt 
											% die Nachbarrelationen m��ten neu erstellt werden
											% hier ist das aber schon zu sp�t
											%FreshGgTList = lists:delete(ActualGgT, GgTList),
											%buildTheRingR(RestOfListWithGgTsMappedOnNeighbours, Nameservice, FreshGgTList)
						end,
						buildTheRingR(RestOfListWithGgTsMappedOnNeighbours, Nameservice).

				
writeToLog(Text) -> werkzeug:logging("Koordinator.log", lists:concat([Text,"\r\n"])).
	

sendKillToGgTsR(_Nameservice, [] ) 		-> true;
sendKillToGgTsR(Nameservice, GgTList) 	-> 	
						[FirstOfGgTList|RestOfGgTList] = GgTList,
						Nameservice ! {self(), {lookup, FirstOfGgTList}},
						receive 	
							{pin,{FirstOfGgTList,GgTNode}} 	->  {FirstOfGgTList,GgTNode} ! kill ;
							not_found  						-> 	true %sollte behandelt werden damit die queue nicht "vollm�llt"
							%after 2000 -> writeToLog(lists:concat(["Nameservice DOWN ?" ,"\r\n"])) 
						end,
						sendKillToGgTsR(Nameservice, RestOfGgTList).

						
toggleKorrigieren(Korrigieren) -> 
						if
							Korrigieren == 0 -> writeToLog(lists:concat([" TOGGLE - Korrigieren wurde von 0 auf 1 geaendert " ,"\r\n"])),
												NewKorrigieren = 1;
							Korrigieren == 1 -> writeToLog(lists:concat([" TOGGLE - Korrigieren wurde von 1 auf 0 geaendert " ,"\r\n"])),
												NewKorrigieren = 0
						end,
						NewKorrigieren.
						
						
% --------------------------------- the ring ------------------------------------

% einfache liste eingeben und liste mit mapping auf nachbarn
initializeSetNeighbours(GgTList) -> 
						ShuffledList = werkzeug:shuffle(GgTList),
						ListWithGgTsMappedOnNeighbours = createNeighbourList(ShuffledList),
						ListWithGgTsMappedOnNeighbours.				

						
% spezialfall 2 ggts - kann nicht behandelt werden
% -  : [{ggTName1, {linkerGgTName, rechterGgTName}},{ggTName2, {linkerGgTName, rechterGgTName}}, ...]
createNeighbourList(GgTList) ->  
						Length = erlang:length(GgTList),
						if
							Length < 2 	-> [];
							Length >= 2 -> 	NewFormatGgTList = appendFirstAndLastAdditionalToList(GgTList),
											ListWithGgTsMappedOnNeighbours = createNeighbourListR(NewFormatGgTList,[]),
											ListWithGgTsMappedOnNeighbours
						end.


createNeighbourListR([_Left,_Actual|[]], ResultList) 		-> 	ResultList;
createNeighbourListR([Left,Actual,Right|Rest], ResultList) 	-> 	NewResultList = lists:append(ResultList,[{Actual,{Left,Right}}]),
																createNeighbourListR([Actual,Right|Rest], NewResultList).

																
% [1,2,3,4] -> [4,1,2,3,4,1]  um einfacher zu iterieren
appendFirstAndLastAdditionalToList([First|Rest]) -> Step1 = lists:append([lists:last(Rest)], [First]),
													Step2 = lists:append(Step1, Rest),
													Step3 = lists:append(Step2, [First]),
													Step3.

